#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <UIKit/UIApplication.h>
#import <LockInfo/Plugin.h>

#import "framework/FWCommon.h"
#import "framework/FWDBAccess.h"
#import "framework/FWApplication.h"
#import "framework/FWApplicationDelegate.h"
#import "framework/FWPreferences.h"
#import "framework/FWListView.h"
#import "framework/FWDetailListView.h"
#import "framework/FWApplication.h"

#import "PIIntegration.h"
#import "PIEventIntegration.h"
#import "PIEventNative.h"
#import "PIEvent.h"
#import "PIAlarm.h"

#import "PICommon.h"
#import "PICalListAccess.h"
#import "PICalPlugin.h"
#import "PICalView.h"
#import "PICalPreview.h"
#import "PICalStatus.h"


@implementation PICalListAccessRange

@synthesize start, duration, color;


+ (PICalListAccessRange*)accessRangeWithStart:(NSInteger)start duration:(NSInteger)duration color:(UIColor *)color
{
    PICalListAccessRange* item = [[[PICalListAccessRange alloc] init] autorelease];
    
    item.start = start;
    item.duration = duration;
    item.color = color;
    
    return item;
}

+ (PICalListAccessRange*)allDayAccessRange:(UIColor *)color
{
    return [PICalListAccessRange accessRangeWithStart:0 duration:-1 color:color];
}

- (BOOL)isAllDay
{
    return duration < 0;
}

@end


@implementation PICalListAccess

@synthesize additionalAlarms;


// ******************************************************************************************
#pragma mark -
#pragma mark Initialisation and object management

- (id)init
{
    if ((self = [super init])) {
    }
    
    return self;
}

- (void)dealloc
{
    [additionalAlarms release];
    
    [super dealloc];
}


// ******************************************************************************************
#pragma mark -
#pragma mark Handling Test events

#ifdef DEVELOPMENT_MODE

- (NSArray*)application:(FWApplication*)application fetchTestDataForPrefs:(FWPreferences*)prefs fromDate:(NSDate*)startDate toDate:(NSDate*)endDate
{
    
    // Single item today from 10:00 - 11:30
    PIEvent* test1 = [PIEvent nativeEventWithKey:@"test1"
                                           title:@"Single item today from 10:00-11:30"
                                            icon:nil
                                        location:@"Test 1 location"
                                       startDate:[NSDate dateTodayAtHour:10 minute:0 section:0]
                                    startDateRaw:0
                                         endDate:[NSDate dateTodayAtHour:11 minute:30 section:0]
                                      endDateRaw:0
                                          allDay:NO
                                        calendar:@"Test 1 calendar"
                                    calendarIcon:nil 
                                   calendarColor:[UIColor redColor]];
    
    // Single item now with alarm 5 mins before
    PIEvent* test3 = [PIEvent nativeEventWithKey:@"test3"
                                           title:@"Single item now with alarm -5"
                                            icon:@"<icon>"
                                        location:nil //@"Test 3 location"
                                       startDate:[NSDate date]
                                    startDateRaw:0
                                         endDate:[NSDate dateWithTimeIntervalSinceNow:60 * 60]
                                      endDateRaw:0
                                          allDay:NO
                                        calendar:@"Test 3 calendar"
                                    calendarIcon:@"<cal-icon>"
                                   calendarColor:[UIColor redColor]];
    
    
    // Single item today in 10 mins with alarm in 2 mins
    PIEvent* test2 = [PIEvent nativeEventWithKey:@"test2"
                                           title:@"Single item today +10 mins with alarm +2"
                                            icon:nil
                                        location:@"Test 2 location with a phone number 0800 123 456 please call"
                                       startDate:[NSDate dateWithTimeIntervalSinceNow:2 * 60]
                                    startDateRaw:0
                                         endDate:[NSDate dateWithTimeIntervalSinceNow:20 * 60]
                                      endDateRaw:0
                                          allDay:NO
                                        calendar:@"Test 2 calendar"
                                    calendarIcon:nil
                                   calendarColor:[UIColor redColor]];
    test2.firstAlarm = test2.nextAlarm = [NSDate dateWithTimeIntervalSinceNow:2 * 60];
    
    // Single item tomorrow now
    PIEvent* test4 = [PIEvent nativeEventWithKey:@"test4"
                                           title:@"Single item tomorrow"
                                            icon:nil
                                        location:@"Test 4 location"
                                       startDate:[NSDate dateWithTimeIntervalSinceNow:SECONDS_PER_DAY]
                                    startDateRaw:0
                                         endDate:[NSDate dateWithTimeIntervalSinceNow:SECONDS_PER_DAY + 20 * 60]
                                      endDateRaw:0
                                          allDay:NO
                                        calendar:@"Test 4 calendar"
                                    calendarIcon:nil
                                   calendarColor:[UIColor redColor]];
    test4.invitation = YES;
    
    // All day today
    PIEvent* test5 = [PIEvent nativeEventWithKey:@"test5"
                                           title:@"All day today"
                                            icon:nil
                                        location:@"Test 5 location"
                                       startDate:[NSDate dateTodayAtHour:0 minute:0 section:0]
                                    startDateRaw:0
                                         endDate:[NSDate dateTodayAtHour:23 minute:59 section:59]
                                      endDateRaw:0
                                          allDay:YES
                                        calendar:@"Test 5 calendar"
                                    calendarIcon:nil
                                   calendarColor:[UIColor blueColor]];
    
    PIEvent* test6 = [PIEvent nativeEventWithKey:@"test5"
                                           title:@"All day today"
                                            icon:nil
                                        location:nil
                                       startDate:[NSDate dateTodayAtHour:0 minute:0 section:0]
                                    startDateRaw:0
                                         endDate:[NSDate dateTodayAtHour:23 minute:59 section:59]
                                      endDateRaw:0
                                          allDay:YES
                                        calendar:@"Test 6 calendar"
                                    calendarIcon:nil
                                   calendarColor:[UIColor yellowColor]];
    
    PIEvent* test7 = [PIEvent nativeEventWithKey:@"test7"
                                           title:@"All day today"
                                            icon:nil
                                        location:@"Test 7 location"
                                       startDate:[[NSDate dateTodayAtHour:0 minute:0 section:0] dateByAddingTimeInterval:SECONDS_PER_DAY * 4]
                                    startDateRaw:0
                                         endDate:[[NSDate dateTodayAtHour:23 minute:59 section:59] dateByAddingTimeInterval:SECONDS_PER_DAY * 4]
                                      endDateRaw:0
                                          allDay:YES
                                        calendar:@"Test 7 calendar"
                                    calendarIcon:nil
                                   calendarColor:[UIColor greenColor]];
    
    PIEvent* test8 = [PIEvent nativeEventWithKey:@"test8"
                                           title:@"All day 3 days"
                                            icon:nil
                                        location:@"Test 8 location"
                                       startDate:[NSDate dateTodayAtHour:0 minute:0 section:0]
                                    startDateRaw:0
                                         endDate:[[NSDate dateTodayAtHour:23 minute:59 section:59] dateByAddingTimeInterval:SECONDS_PER_DAY * 3]
                                      endDateRaw:0
                                          allDay:YES
                                        calendar:@"Test 8 calendar"
                                    calendarIcon:nil
                                   calendarColor:[UIColor greenColor]];
    
    return [NSArray arrayWithObjects:test1, test2, test3, test4, test5, test6, test7, test8, nil];
    //     
    //     [NSMutableDictionary dictionaryWithObjectsAndKeys:
    //                      NSNumberYES, kPICalListAccessFieldIsTest,
    //                      
    //                      @"Single item now with alarm -5", kPICalListAccessFieldTitle,
    //                      
    //                      @"test3", kPICalListAccessFieldKey,
    //                      [NSDate date] , kPICalListAccessFieldStart,
    //                      [NSDate dateWithTimeIntervalSinceNow:60 * 60], kPICalListAccessFieldEnd,
    //                      @"Test 3 location", kPICalListAccessFieldLocation,
    //                      
    //                      @"Test 3 calendar", kPICalListAccessFieldCalendar,
    //                      [UIColor redColor], kPICalListAccessFieldColor,
    //                      
    //                      NSNumberNO, kPICalListAccessFieldIsAllDay,
    //                      NSNumberNO, kPICalListAccessFieldHasAttendees,
    //                      NSNumberYES, kPICalListAccessFieldHasReminder,
    //                      NSNumberNO, kPICalListAccessFieldIsRepeating,
    //                      
    //                      [NSDate dateWithTimeIntervalSinceNow:-5 * 60], kPICalListAccessFieldFirstAlarm,
    //                      [NSDate dateWithTimeIntervalSinceNow:-5 * 60], kPICalListAccessFieldNextAlarm,
    //                      
    //                      NSNumberNO, kPICalListAccessFieldIsPI,
    //                      NSNumberYES, kPICalListAccessFieldHasDetails,
    //                      [NSNumber numberWithInt:kPICalAvailabilityNotSupported], kPICalListAccessFieldAvailability,
    //                      nil]];
    //
    //    // Single item today in 10 mins with alarm in 2 mins
    //    [tests addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
    //                      NSNumberYES, kPICalListAccessFieldIsTest,
    //                      
    //                      @"Single item today +10 mins with alarm +2", kPICalListAccessFieldTitle,
    //                      
    //                      @"test2", kPICalListAccessFieldKey,
    //                      [NSDate dateWithTimeIntervalSinceNow:2*60] , kPICalListAccessFieldStart,
    //                      [NSDate dateWithTimeIntervalSinceNow:20*60], kPICalListAccessFieldEnd,
    //                      @"Test 2 location", kPICalListAccessFieldLocation,
    //                      
    //                      @"Test 2 calendar", kPICalListAccessFieldCalendar,
    //                      [UIColor redColor], kPICalListAccessFieldColor,
    //
    //                      NSNumberNO, kPICalListAccessFieldIsAllDay,
    //                      NSNumberNO, kPICalListAccessFieldHasAttendees,
    //                      NSNumberYES, kPICalListAccessFieldHasReminder,
    //                      NSNumberNO, kPICalListAccessFieldIsRepeating,
    //                      
    //                      [NSDate dateWithTimeIntervalSinceNow:2 * 60], kPICalListAccessFieldFirstAlarm,
    //                      [NSDate dateWithTimeIntervalSinceNow:2 * 60], kPICalListAccessFieldNextAlarm,
    //                      
    //                      NSNumberNO, kPICalListAccessFieldIsPI,
    //                      NSNumberYES, kPICalListAccessFieldHasDetails,
    //                      [NSNumber numberWithInt:kPICalAvailabilityNotSupported], kPICalListAccessFieldAvailability,
    //                      nil]];
    //
    //    // Single item tomorrow now
    //    [tests addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
    //                      NSNumberYES, kPICalListAccessFieldIsTest,
    //                      
    //                      @"Single item tomorrow", kPICalListAccessFieldTitle,
    //                      
    //                      @"test4", kPICalListAccessFieldKey,
    //                      [NSDate dateWithTimeIntervalSinceNow:SECONDS_PER_DAY] , kPICalListAccessFieldStart,
    //                      [NSDate dateWithTimeIntervalSinceNow:SECONDS_PER_DAY + 20*60], kPICalListAccessFieldEnd,
    //                      @"Test 4 location", kPICalListAccessFieldLocation,
    //                      
    //                      @"Test 4 calendar", kPICalListAccessFieldCalendar,
    //                      [UIColor redColor], kPICalListAccessFieldColor,
    //                      
    //                      NSNumberNO, kPICalListAccessFieldIsAllDay,
    //                      NSNumberNO, kPICalListAccessFieldHasAttendees,
    //                      NSNumberYES, kPICalListAccessFieldHasReminder,
    //                      NSNumberNO, kPICalListAccessFieldIsRepeating,
    //                      
    //                      NSNumberNO, kPICalListAccessFieldIsPI,
    //                      NSNumberYES, kPICalListAccessFieldHasDetails,
    //                      [NSNumber numberWithInt:kPICalAvailabilityNotSupported], kPICalListAccessFieldAvailability,
    //                      nil]];
    //
    //    // All day tomorrow
    //    [tests addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
    //                      NSNumberYES, kPICalListAccessFieldIsTest,
    //                      
    //                      @"All day tomorrow", kPICalListAccessFieldTitle,
    //                      
    //                      @"test5", kPICalListAccessFieldKey,
    //                      [NSDate dateWithTimeIntervalSinceNow:SECONDS_PER_DAY] , kPICalListAccessFieldStart,
    //                      [NSDate dateWithTimeIntervalSinceNow:SECONDS_PER_DAY + 20*60], kPICalListAccessFieldEnd,
    //                      @"Test 5 location", kPICalListAccessFieldLocation,
    //                      
    //                      @"Test 5 calendar", kPICalListAccessFieldCalendar,
    //                      [UIColor blueColor], kPICalListAccessFieldColor,
    //                      
    //                      NSNumberYES, kPICalListAccessFieldIsAllDay,
    //                      NSNumberNO, kPICalListAccessFieldHasAttendees,
    //                      NSNumberYES, kPICalListAccessFieldHasReminder,
    //                      NSNumberNO, kPICalListAccessFieldIsRepeating,
    //                      
    //                      NSNumberNO, kPICalListAccessFieldIsPI,
    //                      NSNumberYES, kPICalListAccessFieldHasDetails,
    //                      [NSNumber numberWithInt:kPICalAvailabilityNotSupported], kPICalListAccessFieldAvailability,
    //                      nil]];
    //    [tests addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
    //                      NSNumberYES, kPICalListAccessFieldIsTest,
    //                      
    //                      @"All day tomorrow", kPICalListAccessFieldTitle,
    //                      
    //                      @"test6", kPICalListAccessFieldKey,
    //                      [NSDate dateWithTimeIntervalSinceNow:SECONDS_PER_DAY] , kPICalListAccessFieldStart,
    //                      [NSDate dateWithTimeIntervalSinceNow:SECONDS_PER_DAY + 20*60], kPICalListAccessFieldEnd,
    //                      @"Test 6 location", kPICalListAccessFieldLocation,
    //                      
    //                      @"Test 6 calendar", kPICalListAccessFieldCalendar,
    //                      [UIColor yellowColor], kPICalListAccessFieldColor,
    //                      
    //                      NSNumberYES, kPICalListAccessFieldIsAllDay,
    //                      NSNumberNO, kPICalListAccessFieldHasAttendees,
    //                      NSNumberYES, kPICalListAccessFieldHasReminder,
    //                      NSNumberNO, kPICalListAccessFieldIsRepeating,
    //                      
    //                      NSNumberNO, kPICalListAccessFieldIsPI,
    //                      NSNumberYES, kPICalListAccessFieldHasDetails,
    //                      [NSNumber numberWithInt:kPICalAvailabilityNotSupported], kPICalListAccessFieldAvailability,
    //                      nil]];
    //    [tests addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:
    //                      NSNumberYES, kPICalListAccessFieldIsTest,
    //                      
    //                      @"All day tomorrow", kPICalListAccessFieldTitle,
    //                      
    //                      @"test7", kPICalListAccessFieldKey,
    //                      [NSDate dateWithTimeIntervalSinceNow:SECONDS_PER_DAY] , kPICalListAccessFieldStart,
    //                      [NSDate dateWithTimeIntervalSinceNow:SECONDS_PER_DAY + 20*60], kPICalListAccessFieldEnd,
    //                      @"Test 7 location", kPICalListAccessFieldLocation,
    //                      
    //                      @"Test 7 calendar", kPICalListAccessFieldCalendar,
    //                      [UIColor orangeColor], kPICalListAccessFieldColor,
    //                      
    //                      NSNumberYES, kPICalListAccessFieldIsAllDay,
    //                      NSNumberNO, kPICalListAccessFieldHasAttendees,
    //                      NSNumberYES, kPICalListAccessFieldHasReminder,
    //                      NSNumberNO, kPICalListAccessFieldIsRepeating,
    //                      
    //                      NSNumberNO, kPICalListAccessFieldIsPI,
    //                      NSNumberYES, kPICalListAccessFieldHasDetails,
    //                      [NSNumber numberWithInt:kPICalAvailabilityNotSupported], kPICalListAccessFieldAvailability,
    //                      nil]];
    //
    //    return tests;
}

#endif


// ******************************************************************************************
#pragma mark -
#pragma mark Handling event list

- (NSMutableArray*)mutableAdditionalAlarmsList
{
    NSMutableArray* result = [NSMutableArray arrayWithCapacity:additionalAlarms ? additionalAlarms.count : 0];
    
    if (additionalAlarms) {
        NSDate* now = [NSDate date];
        
        for (PIAlarm* alarm in additionalAlarms) {
            // If date of the entry is not before current date then keep in the list
            if ([alarm.date isEqualOrLaterThanDate:now])
                [result addObject:alarm];
            else
                XLog(@"LI:PICal: Expired extra alarm %@", alarm);
        }
    }
    
    return result;
}

NSInteger compareEvents(id num1, id num2, void *context)
{
    NSComparisonResult result = [((PIEvent*)num1).startDate compare:((PIEvent*)num2).startDate];
    if (result != NSOrderedSame)
        return result;
    else
        return [((PIEvent*)num1).calendar compare:((PIEvent*)num2).calendar];
}

- (NSArray*)application:(FWApplication*)application fetchNewDataForPrefs:(FWPreferences*)prefs
{
    XLog(@"LI:PICal: Updating calendar...");
    
    NSBundle* bundle = [NSBundle bundleForClass:[self class]];
    PICalPlugin* plugin = (PICalPlugin*)application.fwPlugin;
    PICalStatus* status = (PICalStatus*)application.info;
    
    // Read current display settings
    plugin.debugMode = [prefs intValue:kPIOptionsDebugCode withDefault:kPIOptionsDebugCodeDefault] == kDebugModeCode;
    status.viewStyle = (kPICalViewStyle)[prefs intValue:kPICalOptionsViewStyle withDefault:kPICalOptionsViewStyleDefault];
    status.showCalendar = [prefs boolValue:kPICalOptionsShowCalendar withDefault:kPICalOptionsShowCalendarDefault];
    status.shortDates = [prefs intValue:kPIOptionsShortDates withDefault:kPIOptionsShortDatesDefault];
    status.showIcons = [prefs boolValue:kPIOptionsShowIcons withDefault:kPIOptionsShowIconsDefault];
    status.singleLineOniPad = [prefs boolValue:kPIOptionsiPadSingleLine withDefault:kPIOptionsiPadSingleLineDefault];
    status.showAvailability = [prefs boolValue:kPICalOptionsShowAvailability withDefault:kPICalOptionsShowAvailabilityDefault]; 
    status.showEmoji = [prefs boolValue:kPIOptionsShowEmoji withDefault:kPIOptionsShowEmojiDefault]; 
    status.lowColour = [prefs boolValue:kPIOptionsLowColourDisplay withDefault:kPIOptionsLowColourDisplayDefault];
    status.showListDetails = [prefs intValue:kPIOptionsShowDates withDefault:kPIOptionsShowDatesDefault];
    
    plugin.eventStoreUpdated = NO;    // Mark as no longer updated
    
    NSInteger days = [prefs intValue:kPIOptionsDayLimit withDefault:kPIOptionsDayLimitDefault];
    NSInteger weeks = ceil(days / 7.0);
    
    // If skipping weekends then extend the number of days we search
    BOOL skipWeekends = [prefs boolValue:kPIOptionsSkipWeekend withDefault:kPIOptionsSkipWeekendDefault];
    FWWeekdayName weekendStart = (FWWeekdayName)[prefs intValue:kPIOptionsWeekendStart withDefault:kPIOptionsWeekendStartDefault];
    if (weekendStart == FWWeekdayNameUnknown)
        skipWeekends = NO;
    if (skipWeekends)
        days += weeks * 2;
    
    NSDate* midnight = FWLocalDateForLocalMidnight();
    NSDate* endDate = [midnight addTimeInterval:SECONDS_PER_DAY * days];
    NSDate* startDate = [midnight addTimeInterval:-SECONDS_PER_DAY];
    NSDate* now = [NSDate date];
    
#ifdef DEVELOPMENT_MODE
    //    startDate = [midnight addTimeInterval:-SECONDS_PER_DAY * 30];
    //    now = startDate;
    //    midnight = startDate;
#endif
    
    FWDatabase* database = nil;
    @try {
        // Get a single database connection
        database = FWDBConnectDatabase(application.databasePath, application);
        
        XLog(@"LI:PICal: Searching for events in range %@ - %@", startDate, endDate);
        
        NSArray* iosEvents = [PIEventNative  basicEventsList:application prefs:prefs database:database fromDate:startDate toDate:endDate];
        DebugLog(@"LI:PICal: %i events from iOS calendar", iosEvents.count);
        
        NSArray* piEvents = [PIEventIntegration basicEventsList:application prefs:prefs database:database fromDate:startDate toDate:endDate];
        DebugLog(@"LI:PICal: %i events from PI calendar", piEvents.count);
        
        NSMutableArray* rawList = [NSMutableArray arrayWithArray:iosEvents];
        [rawList addObjectsFromArray:piEvents];
        
#ifdef DEVELOPMENT_MODE
        [rawList addObjectsFromArray:[self application:application fetchTestDataForPrefs:prefs fromDate:startDate toDate:endDate]];
#endif    
        
        DebugLog(@"LI:PICal: %i events in initial combined list", [rawList count]);
        
        // Search for entries that span a day. for each create additional entries for the extra days
        NSMutableArray* extras = [NSMutableArray arrayWithCapacity:10];
        // Deliberate crash - working on decoding crash reports
        //    [extras insertObject:@"hello" atIndex:2];
        
        NSCalendar* cal = [NSCalendar autoupdatingCurrentCalendar];
        cal.timeZone = [NSTimeZone systemTimeZone];
        
        int mask = (NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit);
        for (PIEvent* event in rawList) {
            NSDate* start = event.startDate;
            NSDate* end = event.endDate;
            
            // If event spans days then add duplicate entries
            if (!FWIsLocalDateSameDay(start, end)) {
                NSDateComponents* comps = [cal components:mask fromDate:start];
                
                [comps setHour:0];
                [comps setMinute:0];
                [comps setSecond:0];
                NSDate* nextStart = [cal dateFromComponents:comps];
                
                [comps setHour:23];
                [comps setMinute:59];
                [comps setSecond:59];
                NSDate* nextEnd = [cal dateFromComponents:comps];
                NSDate* endFor1stItem = nextEnd;
                
                // Record the original start and end periods
                event.rangeStartDate = start;
                event.rangeEndDate = end;
                NSInteger instance = 0;
                
                do {
                    nextStart = [nextStart addTimeInterval:SECONDS_PER_DAY];
                    nextEnd = [nextEnd addTimeInterval:SECONDS_PER_DAY];
                    
                    PIEvent* newItem = [event copyInstance:++instance];
                    newItem.startDate = nextStart;
                    
                    // Remove items that the replicants shouldn't respond to
                    newItem.firstAlarm = [NSDate nullDate];
                    
                    // if not finishing today then set end time at midnight
                    //if ([nextEnd compare:end] == NSOrderedAscending) {
                    if ([nextEnd isEarlierThanDate:end]) {
                        newItem.endDate = nextEnd;
                        newItem.allDay = YES;
                    }
                    
                    // If the start of this item and the end of the sequence are the same
                    // then this is likely a repeat of an item that was really a 00:00-00:00 all-day
                    // item so we don't want the replicant
                    if ([nextStart isEqualToDate:end])
                        break;
                    
                    [extras addObject:newItem];
                }
                //while (([end compare:nextEnd] == NSOrderedDescending) && ([endDate compare:nextEnd] == NSOrderedDescending));
                while ([end isLaterThanDate:nextEnd] && [endDate isLaterThanDate:nextEnd]);
                
                // Overwrite the end date of the original item
                event.endDate = endFor1stItem;
            }
        }
        [rawList addObjectsFromArray:extras];
        
        DebugLog(@"LI:PICal: %i events in expanded repeating elements list", rawList.count);
        
        // Sort events into ascending order
        if ([rawList count] > 0)
            [rawList sortUsingFunction:compareEvents context:nil];
        
        // Find the first event on each day and mark it as a section header
        NSDate* lastDate = nil;
        NSDate* nextChangeDue = nil;
        NSMutableArray* sections = nil;
        NSMutableArray* filteredList = [NSMutableArray arrayWithCapacity:[rawList count]];
        
        BOOL showWorkingHours = [prefs boolValue:kPIOptionsShowWorkingHours withDefault:kPIOptionsShowWorkingHoursDefault];
        NSInteger dayStart = [prefs intValue:kPIOptionsDayStart withDefault:kPIOptionsDayStartDefault];
        NSInteger dayEnd = [prefs intValue:kPIOptionsDayEnd withDefault:kPIOptionsDayEndDefault];
        if (!showWorkingHours) {
            dayStart = 0;
            dayEnd = SECONDS_PER_DAY;
        }
        
        kPICalDaySummaryStyle summaryStyle = (kPICalDaySummaryStyle)[prefs intValue:kPICalOptionsDaySummary withDefault:kPICalOptionsDaySummaryDefault];
        
        int maxItems = [prefs intValue:kPIOptionsMaxEvents withDefault:kPIOptionsMaxEventsDefault];
        int maxDays = [prefs intValue:kPIOptionsMaxDays withDefault:kPIOptionsMaxDaysDefault];
        BOOL futureEventsOnly = [prefs boolValue:kPICalOptionsFutureOnly withDefault:kPICalOptionsFutureOnlyDefault];
        
        // Do we want the next working day?
        if (maxDays < 0) {
            DebugLog(@"LI:PICal: Calculating next working day from %@", midnight);
            
            maxDays = 1;
            // If we have passed the end of the working day then we want the next day
            NSInteger secsIntoDay = FWSecondsWithinLocalDate(now);
            if (secsIntoDay >= dayEnd) {
                DebugLog(@"LI:PICal: Past day end, skipping to next day");
                ++maxDays;
            }
            
            if (weekendStart != FWWeekdayNameUnknown) {
                do {
                    FWWeekdayName dayName = FWLocalWeekdayName([midnight addTimeInterval:(maxDays - 1) * SECONDS_PER_DAY]);
                    if ((dayName == weekendStart) || (dayName == (weekendStart+1))) {
                        DebugLog(@"LI:PICal: Day is a weekend, skipping to next day");
                        ++maxDays;
                    }
                    else {
                        DebugLog(@"LI:PICal: Day is not a weekend so is OK");
                        break;
                    }
                } while (maxDays < 5);
            }
            
            DebugLog(@"LI:PICal: Next working day range is %d", maxDays);
        }
        
        // Update (and expire any old entries) the list of extra alarms
        NSMutableArray* extraAlarms = [self mutableAdditionalAlarmsList];
        self.additionalAlarms = extraAlarms;
        
        for (PIEvent* event in rawList) {
            // Search for time of next event start or end after the current date. We will
            // need to update then
            NSDate* start = event.startDate;
            NSDate* end = event.endDate;
            
            if (!nextChangeDue || [start isEarlierThanDate:nextChangeDue]) {
                // Set next update if start is in the future
                if ([start isLaterThanDate:now])
                    nextChangeDue = start;
            }
            if (!nextChangeDue || [end isEarlierThanDate:nextChangeDue]) {
                // Set next update if end is in the future
                if ([end isLaterThanDate:now])
                    nextChangeDue = end;
            }
            
            // Have we gone past our end date?
            if ([start isLaterThanDate:endDate]) {
                DebugLog(@"LI:PICal: Item is after end date of list - dropping all further: %@", event.shortDescription);
                break;
            }
            
            // Are we before today?
            if ([start isEarlierThanDate:midnight]) {
                DebugLog(@"LI:PICal: Item is before start of list - ignoring: %@", event.shortDescription);
                continue;
            }
            
            // Skip over the weekend if selected
            if (skipWeekends) {
                FWWeekdayName weekday = FWLocalWeekdayName(start);
                if ((weekday == weekendStart) || (weekday == (weekendStart + 1))) {
                    DebugLog(@"LI:PICal: Item is a weekend - ignoring: %@", event.shortDescription);
                    continue;
                }
            }
            
            if (event.isAllDay ||
                [start isLaterThanDate:now] ||
                ([start isEqualOrEarlierThanDate:now] && [end isLaterThanDate:now] && !futureEventsOnly)) {
                
                // If current date not the same day as the last event then we need a new
                // section header
                if (!lastDate || !FWIsLocalDateSameDay(lastDate, start)) {
                    // Have we recorded enough days?
                    if (--maxDays < 0)
                        break;
                    
                    event.sectionTitle = FWLocalDateStringForUTCDate(start, FWDateFormatterLongFormat, NO, bundle);
                    
                    if (summaryStyle != kPICalDaySummaryStyleNone) {
                        sections = [NSMutableArray arrayWithCapacity:5];
                        event.sectionDetails = sections;
                        event.sectionStartInterval = dayStart;
                        event.sectionEndInterval = dayEnd;
                    }
                }
                
                // Add entry to the summary of time intervals for the section
                if (event.isAllDay) {
                    if (summaryStyle == kPICalDaySummaryStyleFull) {
                        BOOL found = NO;
                        UIColor* color = event.calendarColor;
                        for (PICalListAccessRange* range in sections) {
                            if ((range.duration < 0) && (range.color == color)) {
                                found = YES;
                                break;
                            }
                        }
                        if (!found)
                            [sections addObject:[PICalListAccessRange allDayAccessRange:color]];
                    }
                }
                else {
                    NSInteger begins = FWSecondsWithinLocalDate(start);
                    NSInteger interval = FWSecondsWithinLocalDate(end) - begins;
                    
                    // If end time is before start then it's the next day
                    if (interval < 0)
                        interval = SECONDS_PER_DAY - begins;
                    
                    if (summaryStyle != kPICalDaySummaryStyleNone)
                        [sections addObject:[PICalListAccessRange accessRangeWithStart:begins
                                                                              duration:interval
                                                                                 color:event.calendarColor]];
                }
                
                lastDate = start;
                
                //DebugLog(@"LI:PICal: Adding item to list: %@", entry);
                [filteredList addObject:event];
                
                // Have we recorded enough events?
                if (--maxItems <= 0) {
                    DebugLog(@"LI:PICal: List has enough items - dropping all further");
                    break;
                }
            }
#ifdef DEBUG
            else {
                DebugLog(@"LI:PICal: Item is outside date range of list - ignoring: %@", event.shortDescription);
            }
#endif
        }
        
        [PIEventIntegration addAlarmDataToEventList:application database:database events:filteredList prefs:prefs];
        
        // Find the next due alarm
        NSDate* nextAlarmDue = nil;
        for (PIEvent* event in filteredList) {
            // If there is an entry for this item in the list of extra alarms and it has an
            // earlier alarm date then we will use it for our next alarm
            NSString* key = event.key;
            NSDate* alarm = event.nextAlarm;
            
            for (PIAlarm* extraAlarm in extraAlarms) {
                if ([key isEqualToString:extraAlarm.key]) {
                    if (extraAlarm.type == kPICalAlarmTypeAlarmIsActive) {
                        event.reminderInteract = YES;
                        DebugLog(@"Marked alarm active %@", event.shortDescription);
                    }
                    // If the extra alarm's date is earlier than the current then we want to use it
                    else if ([alarm isNull] || [extraAlarm.date isEarlierThanDate:alarm]) {
                        event.nextAlarm = extraAlarm.date;
                        alarm = extraAlarm.date;
                        
                        DebugLog(@"Set new next alarm %@", extraAlarm);
                    }
#ifdef DEBUG
                    else
                        DebugLog(@"Skipped new next alarm at %@, existing %@", extraAlarm, alarm);
#endif
                }
            }
            
            if (![event.nextAlarm isNull]) {
                if (!nextAlarmDue || [nextAlarmDue isLaterThanDate:event.nextAlarm])
                    nextAlarmDue = event.nextAlarm;
            }
            
#ifdef DEBUG
            //        event.reminderInteract = YES;
#endif
        }
        
        DebugLog(@"LI:PICal: %i events in final list", [filteredList count]);
        
        // Wakeup at the earlier of the next midnight or the next event or the next alarm
        NSDate* nextWakeup = [midnight addTimeInterval:SECONDS_PER_DAY];
        if (nextAlarmDue && [nextAlarmDue isEarlierThanDate:nextWakeup])
            nextWakeup = nextAlarmDue;
        if (nextChangeDue && [nextChangeDue isEarlierThanDate:nextWakeup])
            nextWakeup = nextChangeDue;
        
        if ([nextAlarmDue isNull])
            nextAlarmDue = nil;
        if ([nextWakeup isNull])
            nextWakeup = nil;
        
        status.nextAlarmDue = nextAlarmDue;
        status.nextChangeDue = nextWakeup;
        
        XLog(@"LI:PICal: Next scheduled update will be at %@", nextWakeup);
        XLog(@"LI:PICal: Next scheduled alarm will be at %@", nextAlarmDue);
        
        [plugin notifyNextTimerEventDate:nextWakeup];
        [application.fwPlugin scheduleNextTimerEvent:nextWakeup];
        
        // Use the original data list
        return filteredList;
    }
    @finally {
        FWDBDisconnectDatabase(database);
    }
    
    // No list returned
    return [NSArray array];
}


// ******************************************************************************************
#pragma mark -
#pragma mark Alarm support

- (BOOL)application:(FWApplication*)application shouldUpdateForDate:(NSDate*)date
{
    PICalStatus* info = (PICalStatus*)application.info;
    NSDate* now = [NSDate date];
    
    DebugLog(@"LI:PICal: Next scheduled alarm %@, now = %@", info.nextAlarmDue, now);
    if (info.nextAlarmDue && [info.nextAlarmDue isEqualOrEarlierThanDate:now]) {
        // Check for any newly triggered alarms
        [(PICalPlugin*)application.fwPlugin checkForNewAlarmsForDate:info.nextAlarmDue];
        
        // Update the display
        info.nextAlarmDue = nil;
        return YES;
    }
    
    if (((PICalPlugin*)application.fwPlugin).isEventStoreUpdated) {
        XLog(@"LI:PICal: Event store has been updated");
        return YES;
    }
    else if (info.nextChangeDue && [info.nextChangeDue isEqualOrEarlierThanDate:now]) {
        XLog(@"LI:PICal: Next scheduled update is due");
        return YES;
    }
    else {
        DebugLog(@"LI:PICal: No update required");
        return NO;
    }
}    


// ******************************************************************************************
#pragma mark -
#pragma mark Describing the layout

- (NSInteger)tableView:(LITableView*)tableView totalNumberOfItems:(FWApplication*)application preferences:(FWPreferences*)prefs
{
    PICalStatus* info = (PICalStatus*)application.info;
    return info.itemsReturnedCount;
}

- (BOOL)shouldShowDetailLine:(PIEvent*)event application:(FWApplication*)app preferences:(FWPreferences*)prefs forItem:(BOOL)forItem
{
    PICalStatus* status = (PICalStatus*)app.info;
    
    // Have we been told to override the showing of details?
    NSInteger showDetails = status.showListDetails;
    if (showDetails == ShowDatesNever)
        return NO;
    else if (showDetails == ShowDatesAlways)
        return YES;
    
    if (forItem) {
        if (event.hasDetails)
            return YES;
        //        else if (status.viewStyle == kPICalViewStyleTraditional)
        //            return status.isShowCalendar;
        else
            return status.isShowCalendar;
    }
    else
        return NO;
}

- (CGFloat)tableView:(LITableView *)tableView heightForItem:(NSObject *)item application:(FWApplication *)application preferences:(FWPreferences *)prefs
{
    PIEvent* event = (PIEvent*)item;
    BOOL showDetail = [self shouldShowDetailLine:event application:application preferences:prefs forItem:YES];
    BOOL hasHeader = event.sectionTitle && (event.sectionTitle.length > 0);  
    NSInteger indent = event.isInvitation ? 2 * kFWListViewBorderInset : 0;
    
    CGFloat summaryHeight = [PICalView heightOfSummaryRowWithFont:tableView.theme.summaryStyle.font
                                                       withHeader:hasHeader
                                                 withFollowingRow:showDetail];
    
    if (showDetail) {
        CGFloat detailHeight = [PICalView heightOfDetailRowWithFont:tableView.theme.detailStyle.font];
        return summaryHeight + detailHeight + indent;
    }
    else
        return summaryHeight + indent;
}


// ******************************************************************************************
#pragma mark -
#pragma mark Providing data for the display

- (FWListView*)application:(FWApplication*)app cellViewForTableView:(LITableView*)tableView  preferences:(FWPreferences*)prefs
{
    // We can hard-code the height at this time as this will vary dynamically depending upon content
    return [[PICalView alloc] initWithFrame:CGRectMake(0, 0, tableView.bounds.size.width, 35)
                                      table:tableView application:app];
}

- (void)tableView:(LITableView*)tableView setDetailsUsingView:(FWListView*)view forItem:(NSObject *)item application:(FWApplication *)app preferences:(FWPreferences *)prefs
{
    PICalStatus* info = (PICalStatus*)app.info;
    
    if (item) {
        [(PICalView*)view setViewForTable:tableView
                                    event:(PIEvent*)item
                                    style:info.viewStyle
                               showDetail:(ShowDatesOptions)info.showListDetails
                             showCalendar:info.isShowCalendar
                                 showDate:NO
                         showAvailability:info.isShowAvailability
                                lowColour:info.isLowColour
                              application:app];
    }
    else {
        PICalPlugin* cal = (PICalPlugin*)app.fwPlugin;
        view.summaryText.text = [NSString stringWithFormat:@"up=%@; al=%@",
                                 FWDebugTimeStringForUTCDate(info.nextChangeDue),
                                 FWDebugTimeStringForUTCDate(info.nextAlarmDue)];
        view.detailText.text = [NSString stringWithFormat:@"mu=%@; al=%@",
                                FWDebugTimeStringForUTCDate(cal.muteUntilDate),
                                FWDebugTimeStringForUTCDate(app.fwPlugin.nextScheduledActivity)];
    }
    
    //    // Do we want everything on one line for an iPad?
    //    view.singleLine = FWIsDeviceAnIPad() && info.isSingleLineOniPad;
    //    if (view.singleLine) {
    //        ((PICalView*)view).combineHighlight = NO;
    //    }
}

- (NSString*)tableView:(LITableView*)tableView detailForHeaderInSection:(NSInteger)section application:(FWApplication*)app preferences:(FWPreferences*)prefs
{
    return nil;
}


// ******************************************************************************************
#pragma mark -
#pragma mark Implement preview display

- (UIView*)tableView:(LITableView*)tableView previewWithFrame:(CGRect)rect atIndex:(NSInteger)index forItems:(NSArray*)list application:(FWApplication*)app preferences:(FWPreferences*)prefs
{   
    LIPlugin* plugin = (LIPlugin*)[app.fwPlugin liPlugin];
    BOOL show = [FWPreferences boolValue:kPIOptionsShowPreviews withDefault:kPIOptionsShowPreviewsDefault preferences:plugin.preferences];
    if (!show)
        return nil;
    
    PICalPreview* previewData = [[[PICalPreview alloc] init] autorelease];
    FWDetailListView *thePreview = [[[FWDetailListView alloc] initWithFrame:rect
                                                                       list:list
                                                                      index:index
                                                                    caption:@"ListView/Event %i/%i"
                                                                application:app
                                                                   delegate:previewData] autorelease];
    thePreview.backgroundColor = [UIColor whiteColor];
    
    return thePreview;
}


- (void)scheduleExtraAlarmForId:(NSString*)key atDate:(NSDate*)date type:(kPICalAlarmType)type
{
    XLog(@"LI:PICal: Adding extra alarm instance at %@", date);
    [additionalAlarms addObject:[PIAlarm alarmWithKey:key date:date type:type]];
}


// ******************************************************************************************
#pragma mark -
#pragma mark Libstatusbar support

- (NSString*)libstatusbarIconName
{
    return @"PICalPlugin";
}

- (BOOL)showLibstatusbarIconForItems:(NSArray*)items preferences:(FWPreferences *)prefs
{
    if ([prefs boolValue:kPIOptionsShowStatusbarIcons withDefault:kPIOptionsShowStatusbarIconsDefault]) {
        for (PIEvent* event in items) {
            // If we've reached another day then we don't want an icon
            if (!FWIsLocalDateToday(event.startDate))
                return NO;
            
            // If we have a timed event today then we want an icon
            if (!event.isAllDay)
                return YES;
        }
    }
    
    return NO;
}

@end
